package com.makerworks.medu;

import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;

import org.greenrobot.eventbus.EventBus;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * API 21 以上,可以设置ScanMode,默认ScanSetting为 LOW_LATENCY(低延迟,最高功耗)
 * 因为发现原生系统有bug,不会重复发现信号较强的设备,所以添加 ScanModeHelper 内部类,在判断可能不正常工作的情况下切换到 LOW_POWER
 * Created by liaoyuhao on 2016/7/15.
 */
public class BluetoothAdapter_BLE {

    private static final UUID SERVICE_UUID = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb");
    private static final UUID READ_UUID = UUID.fromString("0000ffe2-0000-1000-8000-00805f9b34fb");
    private static final UUID WRITE_UUID = UUID.fromString("0000ffe3-0000-1000-8000-00805f9b34fb");

    private static final UUID PROGRAM_UUID = UUID.fromString("0000ffe4-0000-1000-8000-00805f9b34fb");
    private static final UUID PROGRAM_CHARACTERISTICS_UUID = UUID.fromString("0000ffe5-0000-1000-8000-00805f9b34fb");

    public static final int STATE_DISCONNECTED = 0;
    public static final int STATE_CONNECTING = 1;
    public static final int STATE_CONNECTED = 2;

    private int mConnectionState = STATE_DISCONNECTED;

    private static final String TAG = BluetoothAdapter_BLE.class.getSimpleName();


    private BluetoothAdapter mBluetoothAdapter;
    protected BluetoothGatt bluetoothGatt;
    private ScanModeHelper mScanModeHelper;
    protected BluetoothGattCharacteristic readCharacteristic;
    protected BluetoothGattCharacteristic writeCharacteristic;
    protected BluetoothGattCharacteristic programCharacteristic;
    private ScanCallback mScanCallback = null;
    private final BleFlowValve bleFlowValve = new BleFlowValve(20, 10);

    public ArrayList<DeviceBean> getDevices() {
        return new ArrayList<DeviceBean>(devices);
    }

    private ArrayList<DeviceBean> devices = new ArrayList<DeviceBean>();

    private boolean isFirstSend = true;
    private boolean hasConnected = false;

    private static BluetoothAdapter_BLE instance;

    public static BluetoothAdapter_BLE getInstance() {
        if (instance == null) {
            synchronized (BluetoothAdapter_BLE.class) {
                if (instance == null) {
                    instance = new BluetoothAdapter_BLE();
                }
            }
        }
        return instance;
    }

    private BluetoothAdapter_BLE() {
        Context context = App.getContext();
        final BluetoothManager bluetoothManager =
                (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();
    }

    private BluetoothAdapter.LeScanCallback mOldScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            onFoundDevice(device, rssi);
        }
    };

    private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            byte[] receiveData = characteristic.getValue();
//            String s = "";
//            for (int i = 0; i < receiveData.length; ++i) {
//                s += String.format(" %02x", receiveData[i]&0x0ff);
//            }
//            Log.d("lyh", "收到数据:" + s);
            onReceiveData(receiveData);
            hasConnected = true;
        }

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                            int newState) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                mConnectionState = STATE_CONNECTED;
                bluetoothGatt.discoverServices();
                isFirstSend = true;
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                BluetoothConnectHelper.onDisconnected();
                if (hasConnected) {
                    //在搜索页断连
                    transmitSearchEvent(SearchEvent.CONNECT_DISCONNECTED);
                } else {
                    transmitSearchEvent(SearchEvent.CONNECT_FAIL);
                }
                hasConnected = false;
                mConnectionState = STATE_DISCONNECTED;
                if (bluetoothGatt != null)
                    bluetoothGatt.disconnect();
            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            isFirstSend = false;
        }

        @Override
        // New services discovered
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                readCharacteristic = bluetoothGatt.getService(SERVICE_UUID).getCharacteristic(READ_UUID);
                writeCharacteristic = bluetoothGatt.getService(SERVICE_UUID).getCharacteristic(WRITE_UUID);
                programCharacteristic = bluetoothGatt.getService(PROGRAM_UUID).getCharacteristic(PROGRAM_CHARACTERISTICS_UUID);
                enableNotification(readCharacteristic);
                transmitSearchEvent(SearchEvent.CONNECT_SUCCEED);
                BluetoothConnectHelper.onConnencted();
//                if (listener != null) {
//                    listener.onConnectedDevice(UnifiedBluetoothManager.getInstance().getDevice(gatt.getDevice().getAddress()));
//                }
            } else {
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }
    };


    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void initScanCallback() {
        if (mScanCallback == null) {
            mScanCallback = new ScanCallback() {

                @Override
                public void onScanFailed(int errorCode) {
                    super.onScanFailed(errorCode);
                }

                @Override
                public void onBatchScanResults(List<ScanResult> results) {
                    super.onBatchScanResults(results);
                }

                @Override
                public void onScanResult(int callbackType, ScanResult result) {
                    BluetoothDevice device = result.getDevice();
                    int rssi = result.getRssi();
                    onFoundDevice(device, rssi);
                }
            };
            mScanModeHelper = new ScanModeHelper();
        }
    }


    protected double calculateAccuracy(int txPower, double rssi) {
        if (rssi == 0) {
            return -1.0; // if we cannot determine accuracy, return -1.
        }
        double ratio = rssi * 1.0 / txPower;
        if (ratio < 1.0) {
            double accuracy = Math.pow(ratio, 10);
            return accuracy;
        } else {
            double accuracy = (0.89976) * Math.pow(ratio, 7.7095) + 0.111;
            return accuracy;
        }
    }

    public boolean isEnable() {
        if (mBluetoothAdapter != null) {
            return mBluetoothAdapter.isEnabled();
        }
        return false;
    }

    public void startDiscovery() {
        devices.clear();
        if (isEnable()) {
            if (Build.VERSION.SDK_INT < 21) {
                mBluetoothAdapter.startLeScan(mOldScanCallback);
            } else {
                initScanCallback();
                mBluetoothAdapter.getBluetoothLeScanner().startScan(null, new ScanSettings.Builder().setScanMode(mScanModeHelper.getScanMode()).build(), mScanCallback);
                mScanModeHelper.onStartScan();
            }
            transmitSearchEvent(SearchEvent.DISCOVER_START);
        } else {
            transmitSearchEvent(SearchEvent.OPEN_BLUETOOTH);
        }

    }

    public void stopDiscovery() {
        if (isEnable()) {
            if (Build.VERSION.SDK_INT < 21) {
                mBluetoothAdapter.stopLeScan(mOldScanCallback);
            } else {
                mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
                mScanModeHelper.onStopScan();
            }
            transmitSearchEvent(SearchEvent.DISCOVER_FINISH);
        }
    }

    public void connect(DeviceBean deviceBean) {
        if (isEnable()) {
            stopDiscovery();
            connectNewDevice(deviceBean);
//            BluetoothDevice lastDevice = null;
//            if (bluetoothGatt != null) {
//                lastDevice = bluetoothGatt.getDevice();
//            }
//            if (deviceBean.equals(lastDevice)) {
//                boolean ok = bluetoothGatt.connect();
//                if (ok) {
//                    transmitSearchEvent(SearchEvent.CONNECT_START);
//                } else {
//                    connectNewDevice(deviceBean);
//                }
//            } else {
//                connectNewDevice(deviceBean);
//            }
        }
    }

    private void connectNewDevice(DeviceBean deviceBean) {
        mConnectionState = STATE_CONNECTING;
        bluetoothGatt = deviceBean.bluetoothDevice.connectGatt(App.getContext(), false, mGattCallback);
        transmitSearchEvent(SearchEvent.CONNECT_START);
    }

    public boolean disconnect() {
        if (isEnable()) {
            if (bluetoothGatt != null) {
                if (mConnectionState == STATE_DISCONNECTED || mConnectionState == STATE_CONNECTING) {
                    transmitSearchEvent(SearchEvent.CONNECT_FAIL);
                }
                readCharacteristic = null;
                writeCharacteristic = null;
                programCharacteristic = null;
                bluetoothGatt.disconnect();
                bluetoothGatt.close();
                return true;
            } else {
                transmitSearchEvent(SearchEvent.CONNECT_FAIL);
            }
            mConnectionState = STATE_DISCONNECTED;
        }
        return false;
    }

    public boolean isDiscovering() {
        return mBluetoothAdapter.isDiscovering();
    }

    // lyh 如果一条数据重试10次还未发送成功,将会被丢弃
    public void write(byte[] bytes) {
        if (isEnable()) {
            bleFlowValve.sendData(new WaitSendData(bytes));
        }
    }

    private boolean enableNotification(BluetoothGattCharacteristic characteristic) {
        if (bluetoothGatt == null || characteristic == null)
            return false;
        if (!bluetoothGatt.setCharacteristicNotification(characteristic, true))
            return false;
        BluetoothGattDescriptor clientConfig = characteristic.getDescriptors().get(0);
        if (clientConfig == null)
            return false;
        clientConfig.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        return bluetoothGatt.writeDescriptor(clientConfig);
    }


    private void onFoundDevice(BluetoothDevice device, int rssi) {
        if (isOurDevice(device)) {
            float distance = -1;
            try {
                int txPower = -59;      //lyh 弃疗了,蓝牙距离这样还不够精确,就得找专业人士来指导了
                distance = (float) calculateAccuracy(txPower, rssi);
                if (distance > 20) {
                    distance = 20;
                }
            } catch (Exception e) {

            }
            onFoundDevice(new DeviceBean(device, distance, rssi));
        }
    }

    private int getContainedIndex(DeviceBean deviceBean) {
        if (devices.size() != 0) {
            int index = 0;
            for (DeviceBean item : devices) {
                if (item.equals(deviceBean)) {
                    return index;
                }
                ++index;
            }
        }
        return -1;
    }

    private void onFoundDevice(DeviceBean device) {
        int index = getContainedIndex(device);
        if (index == -1) {
            device.updateTime = System.currentTimeMillis();
            devices.add(device);
        } else {
            devices.get(index).distance = device.distance;
            devices.get(index).setRssi(device.getRssi());
            devices.get(index).updateTime = System.currentTimeMillis();
        }
    }

    protected boolean isOurDevice(BluetoothDevice device) {
        //name: 1.不能为空  2.包含Makeblock
        if (TextUtils.isEmpty(device.getName())) {
            return false;
        }
        if (!(device.getName().contains("Makeblock")) && !(device.getName().contains("makeblock"))) {
            return false;
        }
        return true;

    }

    private void transmitSearchEvent(int type) {
        EventBus.getDefault().post(new SearchEvent(type));
    }

    private void onReceiveData(byte[] data) {
        BluetoothConnectHelper.onReceiveData(data);
    }

    /**
     * API 21 及以上才会进到这里
     * 这玩意放这里破坏代码结构,但是暂时没想到好的处理方式
     * 经验证,为5.x原生系统的bug,在6.0的 nexus 5 未发现这个问题
     * https://code.google.com/p/android/issues/detail?id=82463
     */
    private class ScanModeHelper {
        private boolean isChanged = false;
        private int scanMode = ScanSettings.SCAN_MODE_LOW_LATENCY;
        private Timer checkScanSettingTimer;

        public ScanModeHelper() {
            super();
            //这里可以加一些机型的判断
//            if () {
//                changeMode();
//            }
        }

        public int getScanMode() {
            return scanMode;
        }

        public void changeMode() {
            if (isChanged) {
                return;
            }
            Log.d("lyh", "changemode");
            isChanged = true;
            scanMode = ScanSettings.SCAN_MODE_LOW_POWER;
            stopDiscovery();
            startDiscovery();
        }

        public void onStartScan() {
            if (isChanged) {
                return;
            }
            //启动一个定时器检验数据
            checkScanSettingTimer = new Timer("checkScanSetting");
            checkScanSettingTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    long currentTime = System.currentTimeMillis();
                    ArrayList<DeviceBean> list = devices;
                    for (DeviceBean deviceBean : list) {
                        //为保险(即时把不该切换的切了,也不能有没切到的情况),分两种情况
                        //第一种,可以理解为就被发现了一次,这个是测试机可以重现的, 正常搜索到的话,不可能会小于-128(见API注释), 而设备之间不能距离太远,极限距离确实可能只发现一次,暂时设置为-80
                        if (deviceBean.updateTime > 0 && currentTime - deviceBean.updateTime > 1000 && deviceBean.getRssi() > -80 && deviceBean.getStableRssi() < -128) {
                            changeMode();
                            return;
                        }
                        //第二种,处于YY中的情况,开始重复发现了几次,但是后面没有再被重复发现,等同于搜索到蓝牙几次,然后蓝牙突然断电的情况,这种情况不一定真的有,但是为保险加上
                        if (deviceBean.updateTime > 0 && currentTime - deviceBean.updateTime > 3000 && deviceBean.getRssi() > -60) {
                            changeMode();
                            return;
                        }
                    }

                }
            }, 0, 300);

        }

        public void onStopScan() {
            //注销定时器
            if (checkScanSettingTimer != null) {
                try {
                    checkScanSettingTimer.cancel();
                } catch (Exception e) {

                } finally {
                    checkScanSettingTimer = null;
                }
            }
        }
    }

    private class BleFlowValve {

        private final LinkedBlockingQueue<WaitSendData> waitSendDataQueue = new LinkedBlockingQueue<WaitSendData>();
        private final Timer sendDataTimer;
        private final int dataMaxLength;

        private byte[] dataCache = new byte[1024];

        private WaitSendData lastData;

        private int cacheCount = 0;


        public boolean sendData(WaitSendData waitSendData) {
            waitSendDataQueue.add(waitSendData);
            return true;
        }

        public BleFlowValve(int dataMaxLength, int timeGap) {
            this.dataMaxLength = dataMaxLength;
            sendDataTimer = new Timer("BleFlowValve", true);
            sendDataTimer.schedule(new SendDataTimerTask(), 0, timeGap);
        }

        private class SendDataTimerTask extends TimerTask {
            @Override
            public void run() {
                while (cacheCount < dataMaxLength && waitSendDataQueue.size() > 0) {
                    WaitSendData waitSendData = waitSendDataQueue.peek();
                    if (lastData == null) {
                        lastData = waitSendData;
                    }
                    byte[] nextData = waitSendData.data;
                    System.arraycopy(nextData, 0, dataCache, cacheCount, nextData.length);
                    cacheCount += nextData.length;
                    if (waitSendData != null) {
                        lastData = waitSendDataQueue.poll();
                    }
                }
                //send
                int sendCount = cacheCount > dataMaxLength ? dataMaxLength : cacheCount;
                if (sendCount == 0) {
                    return;
                } else {
                    try {
                        byte[] data = new byte[sendCount];
                        System.arraycopy(dataCache, 0, data, 0, sendCount);
                        writeCharacteristic.setValue(data);
                        if (bluetoothGatt.writeCharacteristic(writeCharacteristic)) {
                        } else {
                            return;
                        }
                    } catch (Exception e) {
                        Log.e("SendDataTimerTask", e.toString());
                    }
                    cacheCount -= sendCount;
                    if (cacheCount > 0) {
                        System.arraycopy(dataCache, sendCount, dataCache, 0, cacheCount);
                    } else {
                        lastData = null;
                    }
                }
            }
        }

    }
}
